package resolver

import (
	"sync"
	grpclog "tmlake.com/api-gateway/istio-agent/pkg/grpclog/inside"

	"tmlake.com/api-gateway/istio-agent/xds/xdsclient"
	"tmlake.com/api-gateway/istio-agent/xds/xdsclient/xdsresource"
)

type ClusterWrapper struct {
	cluster map[string]*edsHandler
}

// rdsHandlerUpdate wraps the full RouteConfigUpdate that are dynamically
// queried for a given server side listener.
type edsHandlerUpdate struct {
	updates map[string]xdsresource.EndpointsUpdate
	err     error
}

// rdsHandler handles any RDS queries that need to be started for a given server
// side listeners Filter Chains (i.e. not inline).
type edsHandler struct {
	xdsC xdsclient.XDSClient

	mu      sync.Mutex
	updates map[string]xdsresource.EndpointsUpdate
	cancels map[string]func()

	// For a rdsHandler update, the only update wrapped listener cares about is
	// most recent one, so this channel will be opportunistically drained before
	// sending any new updates.
	updateChannel chan edsHandlerUpdate

	clusterUpdate xdsresource.ClusterUpdate

	logger *grpclog.PrefixLogger
}

// newRDSHandler creates a new rdsHandler to watch for RDS resources.
// listenerWrapper updates the list of route names to watch by calling
// updateRouteNamesToWatch() upon receipt of new Listener configuration.
func newEDSHandler(xdsC xdsclient.XDSClient, ch chan edsHandlerUpdate, clusterUpdate xdsresource.ClusterUpdate, logger *grpclog.PrefixLogger) *edsHandler {
	return &edsHandler{
		xdsC:          xdsC,
		updateChannel: ch,
		updates:       make(map[string]xdsresource.EndpointsUpdate),
		cancels:       make(map[string]func()),
		clusterUpdate: clusterUpdate,
		logger:        logger,
	}
}

// updateRouteNamesToWatch handles a list of route names to watch for a given
// server side listener (if a filter chain specifies dynamic RDS configuration).
// This function handles all the logic with respect to any routes that may have
// been added or deleted as compared to what was previously present.
func (eh *edsHandler) updateEndpointNamesToWatch(endpointName string) {
	eh.mu.Lock()
	defer eh.mu.Unlock()
	// Add and start watches for any routes for any new routes in
	// routeNamesToWatch.
	if _, ok := eh.cancels[endpointName]; !ok {
		func(endpointName string) {
			eh.cancels[endpointName] = eh.xdsC.WatchEndpoints(endpointName, func(update xdsresource.EndpointsUpdate, err error) {
				eh.handleEndpointUpdate(endpointName, update, err)
			})
		}(endpointName)
	}

	// Delete and cancel watches for any routes from persisted routeNamesToWatch
	// that are no longer present.
	// grpc server watch one listener
	for endpoint := range eh.cancels {
		if endpoint != endpointName {
			eh.cancels[endpointName]()
			delete(eh.cancels, endpointName)
			delete(eh.updates, endpointName)
		}
	}

	// If the full list (determined by length) of updates are now successfully
	// updated, the listener is ready to be updated.
	if len(eh.updates) == len(eh.cancels) {
		eh.drainAndPush(eh.updateChannel, edsHandlerUpdate{updates: eh.updates})
	}
}

// handleRouteUpdate persists the route config for a given route name, and also
// sends an update to the Listener Wrapper on an error received or if the rds
// handler has a full collection of updates.
func (eh *edsHandler) handleEndpointUpdate(endpointName string, update xdsresource.EndpointsUpdate, err error) {
	if err != nil {
		eh.drainAndPush(eh.updateChannel, edsHandlerUpdate{err: err})
		return
	}
	eh.mu.Lock()
	defer eh.mu.Unlock()
	eh.updates[endpointName] = update

	logger.Infof("handleEndpointUpdate:%+v", update)
	// If the full list (determined by length) of updates have successfully
	// updated, the listener is ready to be updated.
	if len(eh.updates) == len(eh.cancels) {
		eh.drainAndPush(eh.updateChannel, edsHandlerUpdate{updates: eh.updates})
	}
}

func (eh *edsHandler) drainAndPush(ch chan edsHandlerUpdate, update edsHandlerUpdate) {
	select {
	case <-ch:
	default:
	}
	ch <- update
}

// close() is meant to be called by wrapped listener when the wrapped listener
// is closed, and it cleans up resources by canceling all the active RDS
// watches.
func (eh *edsHandler) close() {
	eh.mu.Lock()
	defer eh.mu.Unlock()
	for _, cancel := range eh.cancels {
		cancel()
	}
}
